﻿#region Disclaimer/Info
///////////////////////////////////////////////////////////////////////////////////////////////////
// MetaCollection
// 
// MetaCollection is a system to provide a standard structure, permissions and metadata service
// for content organization.
//
// This project is hosted at Google Code at http://code.google.com/p/metacollection/
//
// This project is licensed under the BSD license.  See the License.txt file for more information.
///////////////////////////////////////////////////////////////////////////////////////////////////
#endregion

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using MetaCollection.Framework.Change;
using MetaCollection.Framework.Model;
using System.Globalization;
using System.Text.RegularExpressions;
using System.Collections;

namespace MetaCollection.Framework.Repository
{
	/// <summary>
	/// The DistributedRepository class provides reference handling features for finding
	/// resources when multiple sources are in use.
	/// </summary>
	/// <typeparam name="T"></typeparam>
	internal abstract class DistributedRepository<T> : IRepository where T : IRepository
	{
		/*
		 * 1. Add repositories.
		 * 2. When performing commands try and use a signature reference to hit the correct repo.
		 *		a. If there is a match then use that repo for the request
		 *		b. If no match then we will need to hit all repos in parallel (or sequence for ease of coding?)
		 *		c. With result (single or end of parallel search) queue a signature in our reference list.
		 * 3. When possible purge old references from the list to keep memory footprint down.
		 * 4. If there is only one repo registered then place code in the signature reference resolution to always
		 *		return a hit on the first repo.
		 */

		IList<T> m_Repositories;
		IDictionary<string, int> m_References;

		/// <summary>
		/// Initializes a new instance of the <see cref="DistributedRepository&lt;T&gt;"/> class.
		/// </summary>
		public DistributedRepository()
		{
			m_Repositories = new List<T>();
			m_References = new Dictionary<string, int>();
		}

		/// <summary>
		/// Initializes a new instance of the Repository
		/// </summary>
		/// <param name="configData"></param>
		public void Initialize(string configData)
		{
		}

		/// <summary>
		/// Gets the default repository.
		/// </summary>
		/// <value>The default repository.</value>
		public T DefaultRepository
		{
			get
			{
				if (m_Repositories.Count < 1)
					throw new InvalidOperationException("No repositories are registered with this Distributed Repository.  At least one repository must be registered before use.");
				return m_Repositories[0];
			}
		}

		/// <summary>
		/// Registers the repository.
		/// </summary>
		/// <param name="repo">The repo.</param>
		/// <exception cref="ArgumentException">Thrown if name already refers to a registered repository</exception>
		public void RegisterRepository(string name, T repo)
		{
			if (string.IsNullOrEmpty(name))
				throw new ArgumentNullException("name");
			if (repo == null)
				throw new ArgumentNullException("repo");

			m_Repositories.Add(repo);
		}

		/// <summary>
		/// Marks the reference for faster resolution of the repository for this key.
		/// </summary>
		/// <param name="key">The key.</param>
		/// <param name="index">The index.</param>
		protected void MarkReference(string key, T repository)
		{
			if (string.IsNullOrEmpty(key))
				throw new ArgumentNullException("key");
			if (repository == null)
				throw new ArgumentNullException("repository");

			int index = m_Repositories.IndexOf(repository);

			if (index < 0)
				throw new ArgumentOutOfRangeException("repository", "Given repository is not registered with this Distributed Repository instance");

			if (index >= 0 && !m_References.ContainsKey(key))
				m_References.Add(key, index);
		}


		/// <summary>
		/// Tracks the references of each repository.  If the repository function produces a non-null
		/// result then a reference is made between the key and the given repository.  This improves 
		/// the resolution time of the ResolveReferences function.
		/// </summary>
		/// <param name="key">The reference key to associate with the repository providing non null results.</param>
		/// <param name="repo">The repository we are testing.</param>
		/// <param name="repositoryFunction">The repository function to execute which will return results we are tracking.</param>
		/// <returns>The result of the repositoryFunction; records a reference as appropriate</returns>
		TResult TrackReference<TResult>(string key, T repo, Func<T, TResult> repositoryFunction)
		{
			TResult result = repositoryFunction(repo);
			if (result != null)
			{
				MarkReference(key, repo);
			}
			return result;
		}

		/// <summary>
		/// Determines whether [is known reference] [the specified key].
		/// </summary>
		/// <param name="key">The key.</param>
		/// <returns>
		/// 	<c>true</c> if [is known reference] [the specified key]; otherwise, <c>false</c>.
		/// </returns>
		protected bool IsKnownReference(string key)
		{
			return m_References.Keys.Contains(key);
		}

		/// <summary>
		/// Gets a value indicating whether this instance is distributed.
		/// </summary>
		/// <value>
		/// 	<c>true</c> if this instance is distributed; otherwise, <c>false</c>.
		/// </value>
		bool IsDistributed { get { return (m_Repositories.Count > 1); } }

		/// <summary>
		/// Resolves the reference.
		/// </summary>
		/// <param name="key">The reference key to look for.</param>
		/// <returns>
		/// The repository or repositories to search for the given id/name
		/// </returns>
		protected IEnumerable<T> ResolveReference(string key)
		{
			if (!IsDistributed)
				yield return DefaultRepository;
			else
			{
				if (IsKnownReference(key))
					yield return m_Repositories[m_References[key]];
				else
				{
					foreach (var repo in m_Repositories)
					{
						yield return repo;
					}
				}
			}
		}

		/// <summary>
		/// Formats the given values into a ReferenceKey.
		/// </summary>
		/// <param name="id">The id of the element which is contained in the repository.</param>
		/// <param name="name">The name of the element contained in the repository (id will refer to parent).</param>
		/// <returns>The reference key</returns>
		protected static string ReferenceKey(Guid id, string name)
		{
			if (!string.IsNullOrEmpty(name))
				return String.Format(CultureInfo.InvariantCulture, "{0}-{1}", id, name);
			else
				return id.ToString();
		}
	}
}
